#!/usr/bin/env python

#
# Aesulate: encrypt RSS/Atom feeds, with in-browser decryption
# 
# Copyright (C) 2009 Grahame Bowland <grahame@angrygoats.net>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
from lxml import etree
from ctr import CTR
import sys, os
import getopt
import hashlib
import hmac

if __name__ == '__main__':
    def usage():
        print >>sys.stderr, "Usage: %s -k <key> <file.rss>" % (sys.argv[0])
        sys.exit(2)

    try:
        opts, args = getopt.getopt(sys.argv[1:], "devk:")
    except:
        usage()

    def find_opt(o):
        try:
            return filter(lambda x: x[0] == o, opts)[0][1]
        except IndexError:
            return None

    verbose = find_opt('-v') is not None

    key = find_opt('-k')
    if key is None:
        usage()
    ctr = CTR(key, 128)
    
    def test_encrypt(s):
        enc = ctr.encrypt(s)
        print "start:", repr(s)
        print "encrypted:", enc
        dec, nonce = ctr.decrypt(enc, True)
        print "got back:", repr(ctr.decrypt(enc))
        assert(dec == s)

    def test_decrypt(s):
        dec = ctr.decrypt(s)
        print "decrypted:", repr(dec)

    def rewrite_rss(et):
        def cipher_text(element):
            text = element.text.encode('utf8')
            enc = ctr.encrypt(text)
            mac = hmac.new(key, enc, hashlib.sha1).hexdigest()[:8]
            element.text = '{AES}' + mac + enc

        def hash_text(element):
            sha1 = hashlib.sha1()
            sha1.update(key)
            sha1.update(element.text)
            element.text = sha1.hexdigest()
            
        def trivial(s):
            return s.strip() == ''

        def for_xpathquery(qf, **namespaces):
            for node in et.xpath(qf, namespaces=namespaces):
                for element in node.xpath('.//text()/..'):
                    # let's avoid totally confusing RSS parsers
                    def nscmp(name):
                        tn = element.tag.lower()
                        if tn == name:
                            return True
                        for namespace in namespaces.values():
                            if tn == '{%s}%s' % (namespace.lower(), name.lower()):
                                return True
                        return False
                    if nscmp('pubdate'):
                        continue
                    if nscmp('modified'):
                        continue
                    if nscmp('issued'):
                        continue
                    if nscmp('id'):
                        hash_text(element)
                        continue
                    if nscmp('guid'):
                        hash_text(element)
                        continue
                    if trivial(element.text):
                        continue
                    cipher_text(element)

	    # RSS
        for_xpathquery('//rss/channel/item')
        for_xpathquery('//a:feed/a:entry', a='http://purl.org/atom/ns#')
        for_xpathquery('//a:feed/a:entry', a='http://www.w3.org/2005/Atom')
        et.write(sys.stdout, xml_declaration=True)

    if find_opt('-d') is not None:
        print "Decryption test:"
        test_decrypt('M7u4SkJCQkI9GQ81AJYtEi/uMezvWvYJVeuJd9Q5z3VFLtDwaIerd/xOPxZJ+uVP')
        test_decrypt('AAECAwQFBgcy+vizFdceHkzH4lEsJHSmsYlksP4eqL/4t53V96CtdNFEmqJhJDbkjZTyYIPt/Po2\nyQEfq8gk1MbVqQ==')        
        test_decrypt('suG4SqSkpKQfLp78hfdGLyyCYi5p7PajC+0p96ryJQAv8mnMn/YrHQnXuBP5mJrfvlEKTCvTQy+Y4Ec9/UrrLwnJBw==')
        test_decrypt('AAAAAAAAAADtwQZ/FkHBox4OPukD7bLn1rbel5xa3mrBMY2QEwM/880VlJbPkRZemlDEyLPQv9Rha0Y03oLrGBpARQ==')
        test_decrypt('AAECAwQFBgcy+vizFdceHkzH4lEsJHSmsYlksP4eqL/4t53V96CtdNFEmqJhJDbkjZTyYIPt/Po2yQEfq8gk1MbVqQ==')
    if find_opt('-e') is not None:
        print "Encryption test:"
        test_encrypt('goats might fly. well they might, for they might fly indeed')
        test_encrypt(''.join([chr(t) for t in xrange(0,255)]))

    if len(args) == 1:
        key = find_opt('-k')
        if key is None:
            print >>sys.stderr, "You must specify a key (-k <key>)."
            sys.exit(2)
        fname = args[0]
        et = etree.parse(open(fname))
        rewrite_rss(et)
    else:
        raise Exception("only one arg allowed")
